  <html>
    <head>
      <meta charset="utf-8">
      <title>SparkSQLCreate Repository</title>
      <style>
        #wrapper {width: 960px; margin: 0 auto;}
        /* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
/* Uncomment @import statement below to use as custom stylesheet */
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,video{display:inline-block}
audio:not([controls]){display:none;height:0}
script{display:none!important}
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
a{background:transparent}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
img,object,svg{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
.center{margin-left:auto;margin-right:auto}
.stretch{width:100%}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:none}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite::before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}
table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
table thead,table tfoot{background:#f7f8f7}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
.clearfix::after,.float-group::after{clear:both}
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}
*:not(pre)>code.nobreak{word-wrap:normal}
*:not(pre)>code.nowrap{white-space:nowrap}
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
em em{font-style:normal}
strong strong{font-weight:400}
.keyseq{color:rgba(51,51,51,.8)}
kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menuref{color:#000}
.menuseq b:not(.caret),.menuref{font-weight:inherit}
.menuseq{word-spacing:-.02em}
.menuseq b.caret{font-size:1.25em;line-height:.8}
.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
b.button::before{content:"[";padding:0 3px 0 2px}
b.button::after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
#content{margin-top:1.25em}
#content::before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span::before{content:"\00a0\2013\00a0"}
#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark::before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber::after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc li{line-height:1.3334;margin-top:.3334em}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}
#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
#content{margin-bottom:.625em}
.sect1{padding-bottom:.625em}
@media screen and (min-width:768px){#content{margin-bottom:1.25em}
.sect1{padding-bottom:1.25em}}
.sect1:last-child{padding-bottom:0}
.sect1+.sect1{border-top:1px solid #e7e7e9}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
.paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6)}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;overflow-x:auto;padding:1em;font-size:.8125em}
@media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}
@media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}
.literalblock pre.nowrap,.literalblock pre.nowrap pre,.listingblock pre.nowrap,.listingblock pre.nowrap pre{white-space:pre;word-wrap:normal}
.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.listingblock>.content{position:relative}
.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
.listingblock:hover code[data-lang]::before{display:block}
.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999}
.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
table.pyhltable td.code{padding-left:.75em;padding-right:0}
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #dddddf}
pre.pygments .lineno{display:inline-block;margin-right:.25em}
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
.verseblock{margin:0 1em 1.25em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
.quoteblock.abstract{margin:0 1em 1.25em;display:block}
.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
.quoteblock.excerpt,.quoteblock .quoteblock{margin:0 0 1.25em;padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;text-align:left;margin-right:0}
table.tableblock{max-width:100%;border-collapse:separate}
p.tableblock:last-child{margin-bottom:0}
td.tableblock>.content{margin-bottom:-1.25em}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}
table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}
table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}
table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px}
table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0}
table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}
table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot,table.frame-ends{border-width:1px 0}
table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7}
table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
td>div.verse{white-space:pre}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
ul.unstyled,ol.unstyled{margin-left:0}
ul.checklist{margin-left:.625em}
ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
ul.inline>li{margin-left:1.25em}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
td.hdlist1{font-weight:bold;padding-bottom:1.25em}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
.colist td:not([class]):first-child img{max-width:none}
.colist td:not([class]):last-child{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left{margin:.25em .625em 1.25em 0}
.imageblock.right{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none;display:inline-block}
a.image object{pointer-events:none}
sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
sup.footnote a,sup.footnoteref a{text-decoration:none}
sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data>table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background-color:#00fafa}
.black{color:#000}
.black-background{background-color:#000}
.blue{color:#0000bf}
.blue-background{background-color:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background-color:#fa00fa}
.gray{color:#606060}
.gray-background{background-color:#7d7d7d}
.green{color:#006000}
.green-background{background-color:#007d00}
.lime{color:#00bf00}
.lime-background{background-color:#00fa00}
.maroon{color:#600000}
.maroon-background{background-color:#7d0000}
.navy{color:#000060}
.navy-background{background-color:#00007d}
.olive{color:#606000}
.olive-background{background-color:#7d7d00}
.purple{color:#600060}
.purple-background{background-color:#7d007d}
.red{color:#bf0000}
.red-background{background-color:#fa0000}
.silver{color:#909090}
.silver-background{background-color:#bcbcbc}
.teal{color:#006060}
.teal-background{background-color:#007d7d}
.white{color:#bfbfbf}
.white-background{background-color:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background-color:#fafa00}
span.icon>.fa{cursor:default}
a span.icon>.fa{cursor:inherit}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]::after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
h1,h2,p,td.content,span.alt{letter-spacing:-.01em}
p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@page{margin:1.25cm .75cm}
@media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
html{font-size:80%}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]::after{content:" (" attr(title) ")"}
pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
thead{display:table-header-group}
svg{max-width:100%}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span::before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]::before{display:block}
#footer{padding:0 .9375em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
@media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem}
.sect1{padding:0!important}
.sect1+.sect1{border:0}
#footer{background:none}
#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}

      </style>
      <link href='https://fonts.googleapis.com/css?family=Noto+Serif' rel='stylesheet' type='text/css'>
      <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,600,600italic,700,700italic,800,800italic' rel='stylesheet' type='text/css'>
      <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/styles/default.min.css">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/asciidoc.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/yaml.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/dockerfile.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/makefile.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/go.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/rust.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/haskell.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/typescript.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/scss.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/less.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/handlebars.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/groovy.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/scala.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/bash.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/languages/ini.min.js"></script>
      <script>hljs.initHighlightingOnLoad();</script>
    </head>
    <body>
      <div id="wrapper">
        <div class="article">
          <h1>SparkSQL</h1>
<div id="preamble">
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>SparkSQL</code> 是什么</p>
</li>
<li>
<p><code>SparkSQL</code> 如何使用</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_1_sparksql_是什么">1. SparkSQL 是什么</a>
<ul class="sectlevel2">
<li><a href="#_1_1_sparksql_的出现契机">1.1. SparkSQL 的出现契机</a></li>
<li><a href="#_1_2_sparksql_的适用场景">1.2. SparkSQL 的适用场景</a></li>
</ul>
</li>
<li><a href="#_2_sparksql_初体验">2. SparkSQL 初体验</a>
<ul class="sectlevel2">
<li><a href="#_2_3_rdd_版本的_wordcount">2.3. RDD 版本的 WordCount</a></li>
<li><a href="#_2_2_命令式_api_的入门案例">2.2. 命令式 API 的入门案例</a></li>
<li><a href="#_2_2_sql_版本_wordcount">2.2. SQL 版本 WordCount</a></li>
</ul>
</li>
<li><a href="#_3_扩展_catalyst_优化器">3. [扩展] Catalyst 优化器</a>
<ul class="sectlevel2">
<li><a href="#_3_1_rdd_和_sparksql_运行时的区别">3.1. RDD 和 SparkSQL 运行时的区别</a></li>
<li><a href="#_3_2_catalyst">3.2. Catalyst</a></li>
</ul>
</li>
<li><a href="#_4_dataset_的特点">4. Dataset 的特点</a></li>
<li><a href="#_5_dataframe_的作用和常见操作">5. DataFrame 的作用和常见操作</a></li>
<li><a href="#_6_dataset_和_dataframe_的异同">6. Dataset 和 DataFrame 的异同</a></li>
<li><a href="#_7_数据读写">7. 数据读写</a>
<ul class="sectlevel2">
<li><a href="#_7_1_初识_dataframereader">7.1. 初识 DataFrameReader</a></li>
<li><a href="#_7_2_初识_dataframewriter">7.2. 初识 DataFrameWriter</a></li>
<li><a href="#_7_3_读写_parquet_格式文件">7.3. 读写 Parquet 格式文件</a></li>
<li><a href="#_7_4_读写_json_格式文件">7.4. 读写 JSON 格式文件</a></li>
<li><a href="#_7_5_访问_hive">7.5. 访问 Hive</a></li>
<li><a href="#_7_6_jdbc">7.6. JDBC</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="sect1">
<h2 id="_1_sparksql_是什么">1. SparkSQL 是什么</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="paragraph">
<p>对于一件事的理解, 应该分为两个大部分, 第一, 它是什么, 第二, 它解决了什么问题</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解为什么会有 <code>SparkSQL</code></p>
</li>
<li>
<p>理解 <code>SparkSQL</code> 所解决的问题, 以及它的使命</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_1_1_sparksql_的出现契机">1.1. SparkSQL 的出现契机</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="paragraph">
<p>理解 <code>SparkSQL</code> 是什么</p>
</div>
</div>
</div>
<div class="exampleblock">
<div class="title">主线</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>历史前提</p>
</li>
<li>
<p>发展过程</p>
</li>
<li>
<p>重要性</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">数据分析的方式</div>
<div class="paragraph">
<p>数据分析的方式大致上可以划分为 <code>SQL</code> 和 命令式两种</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">命令式</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>在前面的 <code>RDD</code> 部分, 非常明显可以感觉的到是命令式的, 主要特征是通过一个算子, 可以得到一个结果, 通过结果再进行后续计算.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">sc.textFile("...")
  .flatMap(_.split(" "))
  .map((_, 1))
  .reduceByKey(_ + _)
  .collect()</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">命令式的优点</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>操作粒度更细, 能够控制数据的每一个处理环节</p>
</li>
<li>
<p>操作更明确, 步骤更清晰, 容易维护</p>
</li>
<li>
<p>支持非结构化数据的操作</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">命令式的缺点</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>需要一定的代码功底</p>
</li>
<li>
<p>写起来比较麻烦</p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">SQL</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>对于一些数据科学家, 要求他们为了做一个非常简单的查询, 写一大堆代码, 明显是一件非常残忍的事情, 所以 <code>SQL on Hadoop</code> 是一个非常重要的方向.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">SELECT
	name,
	age,
	school
FROM students
WHERE age &gt; 10</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">SQL 的优点</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>表达非常清晰, 比如说这段 <code>SQL</code> 明显就是为了查询三个字段, 又比如说这段 <code>SQL</code> 明显能看到是想查询年龄大于 10 岁的条目</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">SQL 的缺点</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>想想一下 3 层嵌套的 <code>SQL</code>, 维护起来应该挺力不从心的吧</p>
</li>
<li>
<p>试想一下, 如果使用 <code>SQL</code> 来实现机器学习算法, 也挺为难的吧</p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="paragraph">
<p><code>SQL</code> 擅长数据分析和通过简单的语法表示查询, 命令式操作适合过程式处理和算法性的处理. 在 <code>Spark</code> 出现之前, 对于结构化数据的查询和处理, 一个工具一向只能支持 <code>SQL</code> 或者命令式, 使用者被迫要使用多个工具来适应两种场景, 并且多个工具配合起来比较费劲.</p>
</div>
<div class="paragraph">
<p>而 <code>Spark</code> 出现了以后, 统一了两种数据处理范式, 是一种革新性的进步.</p>
</div>
</div>
</div>
<div class="paragraph">
<p>因为 <code>SQL</code> 是数据分析领域一个非常重要的范式, 所以 <code>Spark</code> 一直想要支持这种范式, 而伴随着一些决策失误, 这个过程其实还是非常曲折的</p>
</div>
<div class="paragraph">
<p><span class="image"><img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/7a1cdf107b8636713c2502a99d058061.png" alt="7a1cdf107b8636713c2502a99d058061"></span></p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">Hive</dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="dlist">
<dl>
<dt class="hdlist1">解决的问题</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Hive</code> 实现了 <code>SQL on Hadoop</code>, 使用 <code>MapReduce</code> 执行任务</p>
</li>
<li>
<p>简化了 <code>MapReduce</code> 任务</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">新的问题</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Hive</code> 的查询延迟比较高, 原因是<strong>使用 <code>MapReduce</code> 做调度</strong></p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">Shark</dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="dlist">
<dl>
<dt class="hdlist1">解决的问题</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Shark</code> 改写 <code>Hive</code> 的物理执行计划, 使<strong>用 <code>Spark</code> 作业代替 <code>MapReduce</code> 执行物理计划</strong></p>
</li>
<li>
<p>使用列式内存存储</p>
</li>
<li>
<p>以上两点使得 <code>Shark</code> 的查询效率很高</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">新的问题</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Shark</code> 重用了 <code>Hive</code> 的 <code>SQL</code> 解析, 逻辑计划生成以及优化, 所以其实可以认为 <code>Shark</code> 只是把 <code>Hive</code> 的物理执行替换为了 <code>Spark</code> 作业</p>
</li>
<li>
<p>执行计划的生成严重依赖 <code>Hive</code>, 想要增加新的优化非常困难</p>
</li>
<li>
<p><code>Hive</code> 使用 <code>MapReduce</code> 执行作业, <strong>所以 <code>Hive</code> 是进程级别的并行</strong>, 而 <code>Spark</code> 是线程级别的并行, 所以 <code>Hive</code> 中很多线程不安全的代码不适用于 <code>Spark</code></p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
<div class="paragraph">
<p>由于以上问题, <code>Shark</code> 维护了 <code>Hive</code> 的一个分支, 并且无法合并进主线, 难以为继</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>SparkSQL</code></dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="dlist">
<dl>
<dt class="hdlist1">解决的问题</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Spark SQL</code> 使用 <code>Hive</code> 解析 <code>SQL</code> 生成 <code>AST</code> 语法树, 将其后的逻辑计划生成, 优化, 物理计划都自己完成, 而不依赖 <code>Hive</code></p>
</li>
<li>
<p>执行计划和优化交给优化器 <code>Catalyst</code></p>
</li>
<li>
<p>内建了一套简单的 <code>SQL</code> 解析器, 可以不使用 <code>HQL</code>, 此外, 还引入和 <code>DataFrame</code> 这样的 <code>DSL API</code>, 完全可以不依赖任何 <code>Hive</code> 的组件</p>
</li>
<li>
<p><code>Shark</code> 只能查询文件, <code>Spark SQL</code> 可以直接降查询作用于 <code>RDD</code>, 这一点是一个大进步</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">新的问题</dt>
<dd>
<p>对于初期版本的 <code>SparkSQL</code>, 依然有挺多问题, 例如只能支持 <code>SQL</code> 的使用, 不能很好的兼容命令式, 入口不够统一等</p>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Dataset</code></dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p><code>SparkSQL</code> 在 2.0 时代, 增加了一个新的 <code>API</code>, 叫做 <code>Dataset</code>, <code>Dataset</code> 统一和结合了 <code>SQL</code> 的访问和命令式 <code>API</code> 的使用, 这是一个划时代的进步</p>
</div>
<div class="paragraph">
<p>在 <code>Dataset</code> 中可以轻易的做到使用 <code>SQL</code> 查询并且筛选数据, 然后使用命令式 <code>API</code> 进行探索式分析</p>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="title">重要性</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/9b1db9d54c796e0eb6769cafd2ef19ac.png" alt="9b1db9d54c796e0eb6769cafd2ef19ac">
</div>
</div>
<div class="paragraph">
<p><code>SparkSQL</code> 不只是一个 <code>SQL</code> 引擎, <code>SparkSQL</code> 也包含了一套对 <strong>结构化数据的命令式 <code>API</code></strong>, 事实上, 所有 <code>Spark</code> 中常见的工具, 都是依赖和依照于 <code>SparkSQL</code> 的 <code>API</code> 设计的</p>
</div>
</td>
</tr>
</table>
</div>
<div class="exampleblock">
<div class="title">总结: <code>SparkSQL</code> 是什么</div>
<div class="content">
<div class="paragraph">
<p><code>SparkSQL</code> 是一个为了支持 <code>SQL</code> 而设计的工具, 但同时也支持命令式的 <code>API</code></p>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_1_2_sparksql_的适用场景">1.2. SparkSQL 的适用场景</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="paragraph">
<p>理解 <code>SparkSQL</code> 的适用场景</p>
</div>
</div>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top"></th>
<th class="tableblock halign-left valign-top">定义</th>
<th class="tableblock halign-left valign-top">特点</th>
<th class="tableblock halign-left valign-top">举例</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>结构化数据</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">有固定的 <code>Schema</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">有预定义的 <code>Schema</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">关系型数据库的表</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>半结构化数据</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">没有固定的 <code>Schema</code>, 但是有结构</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">没有固定的 <code>Schema</code>, 有结构信息, 数据一般是自描述的</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">指一些有结构的文件格式, 例如 <code>JSON</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>非结构化数据</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">没有固定 <code>Schema</code>, 也没有结构</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">没有固定 <code>Schema</code>, 也没有结构</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">指文档图片之类的格式</p></td>
</tr>
</tbody>
</table>
<div class="dlist">
<dl>
<dt class="hdlist1">结构化数据</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>一般指数据有固定的 <code>Schema</code>, 例如在用户表中, <code>name</code> 字段是 <code>String</code> 型, 那么每一条数据的 <code>name</code> 字段值都可以当作 <code>String</code> 来使用</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code>+----+--------------+---------------------------+-------+---------+
| id | name         | url                       | alexa | country |
+----+--------------+---------------------------+-------+---------+
| 1  | Google       | https://www.google.cm/    | 1     | USA     |
| 2  | 淘宝          | https://www.taobao.com/   | 13    | CN      |
| 3  | 菜鸟教程      | http://www.runoob.com/    | 4689  | CN      |
| 4  | 微博          | http://weibo.com/         | 20    | CN      |
| 5  | Facebook     | https://www.facebook.com/ | 3     | USA     |
+----+--------------+---------------------------+-------+---------+</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">半结构化数据</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>一般指的是数据没有固定的 <code>Schema</code>, 但是数据本身是有结构的</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">{
     "firstName": "John",
     "lastName": "Smith",
     "age": 25,
     "phoneNumber":
     [
         {
           "type": "home",
           "number": "212 555-1234"
         },
         {
           "type": "fax",
           "number": "646 555-4567"
         }
     ]
 }</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">没有固定 <code>Schema</code></dt>
<dd>
<p>指的是半结构化数据是没有固定的 <code>Schema</code> 的, 可以理解为没有显式指定 <code>Schema</code><br>
比如说一个用户信息的 <code>JSON</code> 文件, 第一条数据的 <code>phone_num</code> 有可能是 <code>String</code>, 第二条数据虽说应该也是 <code>String</code>, 但是如果硬要指定为 <code>BigInt</code>, 也是有可能的<br>
因为没有指定 <code>Schema</code>, 没有显式的强制的约束</p>
</dd>
<dt class="hdlist1">有结构</dt>
<dd>
<p>虽说半结构化数据是没有显式指定 <code>Schema</code> 的, 也没有约束, 但是半结构化数据本身是有有隐式的结构的, 也就是数据自身可以描述自身<br>
例如 <code>JSON</code> 文件, 其中的某一条数据是有字段这个概念的, 每个字段也有类型的概念, 所以说 <code>JSON</code> 是可以描述自身的, 也就是数据本身携带有元信息</p>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>SparkSQL</code> 处理什么数据的问题?</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>Spark</code> 的 <code>RDD</code> 主要用于处理 <strong>非结构化数据</strong> 和 <strong>半结构化数据</strong></p>
</li>
<li>
<p><code>SparkSQL</code> 主要用于处理 <strong>结构化数据</strong></p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1"><code>SparkSQL</code> 相较于 <code>RDD</code> 的优势在哪?</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>SparkSQL</code> 提供了更好的外部数据源读写支持</p>
<div class="ulist">
<ul>
<li>
<p>因为大部分外部数据源是有结构化的, 需要在 <code>RDD</code> 之外有一个新的解决方案, 来整合这些结构化数据源</p>
</li>
</ul>
</div>
</li>
<li>
<p><code>SparkSQL</code> 提供了直接访问列的能力</p>
<div class="ulist">
<ul>
<li>
<p>因为 <code>SparkSQL</code> 主要用做于处理结构化数据, 所以其提供的 <code>API</code> 具有一些普通数据库的能力</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
</dd>
</dl>
</div>
<div class="exampleblock">
<div class="title">总结: <code>SparkSQL</code> 适用于什么场景?</div>
<div class="content">
<div class="paragraph">
<p><code>SparkSQL</code> 适用于处理结构化数据的场景</p>
</div>
</div>
</div>
<div class="exampleblock">
<div class="title">本章总结</div>
<div class="content">
<div class="ulist">
<ul>
<li>
<p><code>SparkSQL</code> 是一个即支持 <code>SQL</code> 又支持命令式数据处理的工具</p>
</li>
<li>
<p><code>SparkSQL</code> 的主要适用场景是处理结构化数据</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_2_sparksql_初体验">2. SparkSQL 初体验</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>了解 <code>SparkSQL</code> 的 <code>API</code> 由哪些部分组成</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_2_3_rdd_版本的_wordcount">2.3. RDD 版本的 WordCount</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)

sc.textFile("hdfs://node01:8020/dataset/wordcount.txt")
  .flatMap(_.split(" "))
  .map((_, 1))
  .reduceByKey(_ + _)
  .collect</code></pre>
</div>
</div>
<div class="ulist">
<ul>
<li>
<p><code>RDD</code> 版本的代码有一个非常明显的特点, 就是它所处理的数据是基本类型的, 在算子中对整个数据进行处理</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="_2_2_命令式_api_的入门案例">2.2. 命令式 API 的入门案例</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code>case class People(name: String, age: Int)

val spark: SparkSession = new sql.SparkSession.Builder()       <i class="conum" data-value="1"></i><b>(1)</b>
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val peopleRDD: RDD[People] = spark.sparkContext.parallelize(Seq(People("zhangsan", 9), People("lisi", 15)))
val peopleDS: Dataset[People] = peopleRDD.toDS()               <i class="conum" data-value="2"></i><b>(2)</b>
val teenagers: Dataset[String] = peopleDS.where('age &gt; 10)     <i class="conum" data-value="3"></i><b>(3)</b>
  .where('age &lt; 20)
  .select('name)
  .as[String]

/*
+----+
|name|
+----+
|lisi|
+----+
*/
teenagers.show()</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>SparkSQL 中有一个新的入口点, 叫做 SparkSession</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>SparkSQL 中有一个新的类型叫做 Dataset</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>SparkSQL 有能力直接通过字段名访问数据集, 说明 SparkSQL 的 API 中是携带 Schema 信息的</td>
</tr>
</table>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">SparkSession</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>SparkContext</code> 作为 <code>RDD</code> 的创建者和入口, 其主要作用有如下两点</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>创建 <code>RDD</code>, 主要是通过读取文件创建 <code>RDD</code></p>
</li>
<li>
<p>监控和调度任务, 包含了一系列组件, 例如 <code>DAGScheduler</code>, <code>TaskSheduler</code></p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1">为什么无法使用 <code>SparkContext</code> 作为 <code>SparkSQL</code> 的入口?</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>SparkContext</code> 在读取文件的时候, 是不包含 <code>Schema</code> 信息的, 因为读取出来的是 <code>RDD</code></p>
</li>
<li>
<p><code>SparkContext</code> 在整合数据源如 <code>Cassandra</code>, <code>JSON</code>, <code>Parquet</code> 等的时候是不灵活的, 而 <code>DataFrame</code> 和 <code>Dataset</code> 一开始的设计目标就是要支持更多的数据源</p>
</li>
<li>
<p><code>SparkContext</code> 的调度方式是直接调度 <code>RDD</code>, 但是一般情况下针对结构化数据的访问, 会先通过优化器优化一下</p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
<div class="paragraph">
<p>所以 <code>SparkContext</code> 确实已经不适合作为 <code>SparkSQL</code> 的入口, 所以刚开始的时候 <code>Spark</code> 团队为 <code>SparkSQL</code> 设计了两个入口点, 一个是 <code>SQLContext</code> 对应 <code>Spark</code> 标准的 <code>SQL</code> 执行, 另外一个是 <code>HiveContext</code> 对应 <code>HiveSQL</code> 的执行和 <code>Hive</code> 的支持.</p>
</div>
<div class="paragraph">
<p>在 <code>Spark 2.0</code> 的时候, 为了解决入口点不统一的问题, 创建了一个新的入口点 <code>SparkSession</code>, 作为整个 <code>Spark</code> 生态工具的统一入口点, 包括了 <code>SQLContext</code>, <code>HiveContext</code>, <code>SparkContext</code> 等组件的功能</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">新的入口应该有什么特性?</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p>能够整合 <code>SQLContext</code>, <code>HiveContext</code>, <code>SparkContext</code>, <code>StreamingContext</code> 等不同的入口点</p>
</li>
<li>
<p>为了支持更多的数据源, 应该完善读取和写入体系</p>
</li>
<li>
<p>同时对于原来的入口点也不能放弃, 要向下兼容</p>
</li>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
<div class="sidebarblock">
<div class="content">
<div class="title">DataFrame &amp; Dataset</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/eca0d2e1e2b5ce678161438d87707b61.png" alt="eca0d2e1e2b5ce678161438d87707b61">
</div>
</div>
<div class="paragraph">
<p><code>SparkSQL</code> 最大的特点就是它针对于结构化数据设计, 所以 <code>SparkSQL</code> 应该是能支持针对某一个字段的访问的, 而这种访问方式有一个前提, 就是 <code>SparkSQL</code> 的数据集中, 要 <strong>包含结构化信息</strong>, 也就是俗称的 <code>Schema</code></p>
</div>
<div class="paragraph">
<p>而 <code>SparkSQL</code> 对外提供的 <code>API</code> 有两类, 一类是直接执行 <code>SQL</code>, 另外一类就是命令式. <code>SparkSQL</code> 提供的命令式 <code>API</code> 就是 <code>DataFrame</code> 和 <code>Dataset</code>, 暂时也可以认为 <code>DataFrame</code> 就是 <code>Dataset</code>, 只是在不同的 <code>API</code> 中返回的是 <code>Dataset</code> 的不同表现形式</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">// RDD
rdd.map { case Person(id, name, age) =&gt; (age, 1) }
  .reduceByKey {case ((age, count), (totalAge, totalCount)) =&gt; (age, count + totalCount)}

// DataFrame
df.groupBy("age").count("age")</code></pre>
</div>
</div>
<div class="paragraph">
<p>通过上面的代码, 可以清晰的看到, <code>SparkSQL</code> 的命令式操作相比于 <code>RDD</code> 来说, 可以直接通过 <code>Schema</code> 信息来访问其中某个字段, 非常的方便</p>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_2_2_sql_版本_wordcount">2.2. SQL 版本 WordCount</h3>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val peopleRDD: RDD[People] = spark.sparkContext.parallelize(Seq(People("zhangsan", 9), People("lisi", 15)))
val peopleDS: Dataset[People] = peopleRDD.toDS()
peopleDS.createOrReplaceTempView("people")

val teenagers: DataFrame = spark.sql("select name from people where age &gt; 10 and age &lt; 20")

/*
+----+
|name|
+----+
|lisi|
+----+
 */
teenagers.show()</code></pre>
</div>
</div>
<div class="paragraph">
<p>以往使用 <code>SQL</code> 肯定是要有一个表的, 在 <code>Spark</code> 中, 并不存在表的概念, 但是有一个近似的概念, 叫做 <code>DataFrame</code>, 所以一般情况下要先通过 <code>DataFrame</code> 或者 <code>Dataset</code> 注册一张临时表, 然后使用 <code>SQL</code> 操作这张临时表</p>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="paragraph">
<p><code>SparkSQL</code> 提供了 <code>SQL</code> 和 命令式 <code>API</code> 两种不同的访问结构化数据的形式, 并且它们之间可以无缝的衔接</p>
</div>
<div class="paragraph">
<p>命令式 <code>API</code> 由一个叫做 <code>Dataset</code> 的组件提供, 其还有一个变形, 叫做 <code>DataFrame</code></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_3_扩展_catalyst_优化器">3. [扩展] Catalyst 优化器</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>SparkSQL</code> 和以 <code>RDD</code> 为代表的 <code>SparkCore</code> 最大的区别</p>
</li>
<li>
<p>理解优化器的运行原理和作用</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_3_1_rdd_和_sparksql_运行时的区别">3.1. RDD 和 SparkSQL 运行时的区别</h3>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>RDD</code> 的运行流程</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/1e627dcc1dc31f721933d3e925fa318b.png" alt="1e627dcc1dc31f721933d3e925fa318b">
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">大致运行步骤</dt>
<dd>
<p>先将 <code>RDD</code> 解析为由 <code>Stage</code> 组成的 <code>DAG</code>, 后将 <code>Stage</code> 转为 <code>Task</code> 直接运行</p>
</dd>
<dt class="hdlist1">问题</dt>
<dd>
<p>任务会按照代码所示运行, 依赖开发者的优化, 开发者的会在很大程度上影响运行效率</p>
</dd>
<dt class="hdlist1">解决办法</dt>
<dd>
<p>创建一个组件, 帮助开发者修改和优化代码, 但是这在 <code>RDD</code> 上是无法实现的</p>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">为什么 <code>RDD</code> 无法自我优化?</dt>
<dd>
<div class="ulist">
<ul>
<li>
<p><code>RDD</code> 没有 <code>Schema</code> 信息</p>
</li>
<li>
<p><code>RDD</code> 可以同时处理结构化和非结构化的数据</p>
</li>
</ul>
</div>
</dd>
<dt class="hdlist1"><code>SparkSQL</code> 提供了什么?</dt>
<dd>
<div class="openblock">
<div class="content">
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/72e4d163c029f86fafcfa083e6cf6eda.png" alt="72e4d163c029f86fafcfa083e6cf6eda">
</div>
</div>
<div class="paragraph">
<p>和 <code>RDD</code> 不同, <code>SparkSQL</code> 的 <code>Dataset</code> 和 <code>SQL</code> 并不是直接生成计划交给集群执行, 而是经过了一个叫做 <code>Catalyst</code> 的优化器, 这个优化器能够自动帮助开发者优化代码</p>
</div>
<div class="paragraph">
<p>也就是说, 在 <code>SparkSQL</code> 中, 开发者的代码即使不够优化, 也会被优化为相对较好的形式去执行</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">为什么 <code>SparkSQL</code> 提供了这种能力?</dt>
<dd>
<p>首先, <code>SparkSQL</code> 大部分情况用于处理结构化数据和半结构化数据, 所以 <code>SparkSQL</code> 可以获知数据的 <code>Schema</code>, 从而根据其 <code>Schema</code> 来进行优化</p>
</dd>
</dl>
</div>
</div>
</div>
</dd>
</dl>
</div>
</div>
<div class="sect2">
<h3 id="_3_2_catalyst">3.2. Catalyst</h3>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>为了解决过多依赖 <code>Hive</code> 的问题, <code>SparkSQL</code> 使用了一个新的 <code>SQL</code> 优化器替代 <code>Hive</code> 中的优化器, 这个优化器就是 <code>Catalyst</code>, 整个 <code>SparkSQL</code> 的架构大致如下</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/4d025ea8579395f704702eb94572b8de.png" alt="4d025ea8579395f704702eb94572b8de">
</div>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>API</code> 层简单的说就是 <code>Spark</code> 会通过一些 <code>API</code> 接受 <code>SQL</code> 语句</p>
</li>
<li>
<p>收到 <code>SQL</code> 语句以后, 将其交给 <code>Catalyst</code>, <code>Catalyst</code> 负责解析 <code>SQL</code>, 生成执行计划等</p>
</li>
<li>
<p><code>Catalyst</code> 的输出应该是 <code>RDD</code> 的执行计划</p>
</li>
<li>
<p>最终交由集群运行</p>
</li>
</ol>
</div>
</td>
</tr>
</table>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/67b14d92b21b191914800c384cbed439.png" alt="67b14d92b21b191914800c384cbed439">
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">Step 1 : 解析 <code>SQL</code>, 并且生成 <code>AST</code> (抽象语法树)</dt>
<dd>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/5c0e91faae9043400c11bf68c20031a2.png" alt="5c0e91faae9043400c11bf68c20031a2">
</div>
</div>
</dd>
<dt class="hdlist1">Step 2 : 在 <code>AST</code> 中加入元数据信息, 做这一步主要是为了一些优化, 例如 <code>col = col</code> 这样的条件, 下图是一个简略图, 便于理解 </dt>
<dd>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/02afbb7533249cc6024c2dfc2ee4891e.png" alt="02afbb7533249cc6024c2dfc2ee4891e">
</div>
</div>
<div class="openblock">
<div class="content">
<div class="ulist">
<ul>
<li>
<p><code>score.id &#8594; id#1#L</code> 为 <code>score.id</code> 生成 <code>id</code> 为 1, 类型是 <code>Long</code></p>
</li>
<li>
<p><code>score.math_score &#8594; math_score#2#L</code> 为 <code>score.math_score</code> 生成 <code>id</code> 为 2, 类型为 <code>Long</code></p>
</li>
<li>
<p><code>people.id &#8594; id#3#L</code> 为 <code>people.id</code> 生成 <code>id</code> 为 3, 类型为 <code>Long</code></p>
</li>
<li>
<p><code>people.age &#8594; age#4#L</code> 为 <code>people.age</code> 生成 <code>id</code> 为 4, 类型为 <code>Long</code></p>
</li>
</ul>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">Step 3 : 对已经加入元数据的 <code>AST</code>, 输入优化器, 进行优化, 从两种常见的优化开始, 简单介绍 </dt>
<dd>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/07142425c65dc6d921451a8bdec8a29d.png" alt="07142425c65dc6d921451a8bdec8a29d">
</div>
</div>
<div class="openblock">
<div class="content">
<div class="ulist">
<ul>
<li>
<p>谓词下推 <code>Predicate Pushdown</code>, 将 <code>Filter</code> 这种可以减小数据集的操作下推, 放在 <code>Scan</code> 的位置, 这样可以减少操作时候的数据量</p>
</li>
</ul>
</div>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/7b58443ef6ace60d269d704c1f4eae21.png" alt="7b58443ef6ace60d269d704c1f4eae21">
</div>
</div>
<div class="openblock">
<div class="content">
<div class="ulist">
<ul>
<li>
<p>列值裁剪 <code>Column Pruning</code>, 在谓词下推后, <code>people</code> 表之上的操作只用到了 <code>id</code> 列, 所以可以把其它列裁剪掉, 这样可以减少处理的数据量, 从而优化处理速度</p>
</li>
</ul>
</div>
</div>
</div>
<div class="openblock">
<div class="content">
<div class="ulist">
<ul>
<li>
<p>还有其余很多优化点, 大概一共有一二百种, 随着 <code>SparkSQL</code> 的发展, 还会越来越多, 感兴趣的同学可以继续通过源码了解, 源码在  <code>org.apache.spark.sql.catalyst.optimizer.Optimizer</code></p>
</li>
</ul>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">Step 4 : 上面的过程生成的 <code>AST</code> 其实最终还没办法直接运行, 这个 <code>AST</code> 叫做 <code>逻辑计划</code>, 结束后, 需要生成 <code>物理计划</code>, 从而生成 <code>RDD</code> 来运行 </dt>
<dd>
<div class="openblock">
<div class="content">
<div class="ulist">
<ul>
<li>
<p>在生成`物理计划`的时候, 会经过`成本模型`对整棵树再次执行优化, 选择一个更好的计划</p>
</li>
<li>
<p>在生成`物理计划`以后, 因为考虑到性能, 所以会使用代码生成, 在机器中运行</p>
</li>
</ul>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="dlist">
<dl>
<dt class="hdlist1">可以使用 <code>queryExecution</code> 方法查看逻辑执行计划, 使用 <code>explain</code> 方法查看物理执行计划</dt>
<dd>
<div class="imageblock">
<div class="content">
<img src="http://nos.netease.com/knowledge/6dd59b15-d810-4f1e-ab52-c1ecfe0bddcd" alt="6dd59b15 d810 4f1e ab52 c1ecfe0bddcd" width="700">
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="http://nos.netease.com/knowledge/6281b141-af94-41e7-8953-d33b0a6d04d0" alt="6281b141 af94 41e7 8953 d33b0a6d04d0" width="700">
</div>
</div>
</dd>
<dt class="hdlist1">也可以使用 <code>Spark WebUI</code> 进行查看</dt>
<dd>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/7884408908284ba4ebc57b0f1360bc03.png" alt="7884408908284ba4ebc57b0f1360bc03" width="700">
</div>
</div>
</dd>
</dl>
</div>
</td>
</tr>
</table>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="paragraph">
<p><code>SparkSQL</code> 和 <code>RDD</code> 不同的主要点是在于其所操作的数据是结构化的, 提供了对数据更强的感知和分析能力, 能够对代码进行更深层的优化, 而这种能力是由一个叫做 <code>Catalyst</code> 的优化器所提供的</p>
</div>
<div class="paragraph">
<p><code>Catalyst</code> 的主要运作原理是分为三步, 先对 <code>SQL</code> 或者 <code>Dataset</code> 的代码解析, 生成逻辑计划, 后对逻辑计划进行优化, 再生成物理计划, 最后生成代码到集群中以 <code>RDD</code> 的形式运行</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_4_dataset_的特点">4. Dataset 的特点</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>Dataset</code> 是什么</p>
</li>
<li>
<p>理解 <code>Dataset</code> 的特性</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>Dataset</code> 是什么?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15)))
// 方式1: 通过对象来处理
dataset.filter(item =&gt; item.age &gt; 10).show()
// 方式2: 通过字段来处理
dataset.filter('age &gt; 10).show()
// 方式3: 通过类似SQL的表达式来处理
dataset.filter("age &gt; 10").show()</code></pre>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">问题1: <code>People</code> 是什么?</dt>
<dd>
<p><code>People</code> 是一个强类型的类</p>
</dd>
<dt class="hdlist1">问题2: 这个 <code>Dataset</code> 中是结构化的数据吗?</dt>
<dd>
<p>非常明显是的, 因为 <code>People</code> 对象中有结构信息, 例如字段名和字段类型</p>
</dd>
<dt class="hdlist1">问题3: 这个 <code>Dataset</code> 能够使用类似 <code>SQL</code> 这样声明式结构化查询语句的形式来查询吗?</dt>
<dd>
<p>当然可以, 已经演示过了</p>
</dd>
<dt class="hdlist1">问题4: <code>Dataset</code> 是什么?</dt>
<dd>
<p><code>Dataset</code> 是一个强类型, 并且类型安全的数据容器, 并且提供了结构化查询 <code>API</code> 和类似 <code>RDD</code> 一样的命令式 <code>API</code></p>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">即使使用 <code>Dataset</code> 的命令式 <code>API</code>, 执行计划也依然会被优化</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>Dataset</code> 具有 <code>RDD</code> 的方便, 同时也具有 <code>DataFrame</code> 的性能优势, 并且 <code>Dataset</code> 还是强类型的, 能做到类型安全.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">scala&gt; spark.range(1).filter('id === 0).explain(true)

== Parsed Logical Plan ==
'Filter ('id = 0)
+- Range (0, 1, splits=8)

== Analyzed Logical Plan ==
id: bigint
Filter (id#51L = cast(0 as bigint))
+- Range (0, 1, splits=8)

== Optimized Logical Plan ==
Filter (id#51L = 0)
+- Range (0, 1, splits=8)

== Physical Plan ==
*Filter (id#51L = 0)
+- *Range (0, 1, splits=8)</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Dataset</code> 的底层是什么?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>Dataset</code> 最底层处理的是对象的序列化形式, 通过查看 <code>Dataset</code> 生成的物理执行计划, 也就是最终所处理的 <code>RDD</code>, 就可以判定 <code>Dataset</code> 底层处理的是什么形式的数据</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15)))
val internalRDD: RDD[InternalRow] = dataset.queryExecution.toRdd</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>dataset.queryExecution.toRdd</code> 这个 <code>API</code> 可以看到 <code>Dataset</code> 底层执行的 <code>RDD</code>, 这个 <code>RDD</code> 中的范型是 <code>InternalRow</code>, <code>InternalRow</code> 又称之为 <code>Catalyst Row</code>, 是 <code>Dataset</code> 底层的数据结构, 也就是说, 无论 <code>Dataset</code> 的范型是什么, 无论是 <code>Dataset[Person]</code> 还是其它的, 其最底层进行处理的数据结构都是 <code>InternalRow</code></p>
</div>
<div class="paragraph">
<p>所以, <code>Dataset</code> 的范型对象在执行之前, 需要通过 <code>Encoder</code> 转换为 <code>InternalRow</code>, 在输入之前, 需要把 <code>InternalRow</code> 通过 <code>Decoder</code> 转换为范型对象</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/cc610157b92466cac52248a8bf72b76e.png" alt="cc610157b92466cac52248a8bf72b76e">
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">可以获取 <code>Dataset</code> 对应的 <code>RDD</code> 表示</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>在 <code>Dataset</code> 中, 可以使用一个属性 <code>rdd</code> 来得到它的 <code>RDD</code> 表示, 例如 <code>Dataset[T] &#8594; RDD[T]</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15)))

/*
(2) MapPartitionsRDD[3] at rdd at Testing.scala:159 []
 |  MapPartitionsRDD[2] at rdd at Testing.scala:159 []
 |  MapPartitionsRDD[1] at rdd at Testing.scala:159 []
 |  ParallelCollectionRDD[0] at rdd at Testing.scala:159 []
 */
<i class="conum" data-value="1"></i><b>(1)</b>
println(dataset.rdd.toDebugString) // 这段代码的执行计划为什么多了两个步骤?

/*
(2) MapPartitionsRDD[5] at toRdd at Testing.scala:160 []
 |  ParallelCollectionRDD[4] at toRdd at Testing.scala:160 []
 */
<i class="conum" data-value="2"></i><b>(2)</b>
println(dataset.queryExecution.toRdd.toDebugString)</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>使用 <code>Dataset.rdd</code> 将 <code>Dataset</code> 转为 <code>RDD</code> 的形式</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td><code>Dataset</code> 的执行计划底层的 <code>RDD</code></td>
</tr>
</table>
</div>
<div class="paragraph">
<p>可以看到 <code>(1)</code> 对比 <code>(2)</code> 对了两个步骤, 这两个步骤的本质就是将 <code>Dataset</code> 底层的 <code>InternalRow</code> 转为 <code>RDD</code> 中的对象形式, 这个操作还是会有点重的, 所以慎重使用 <code>rdd</code> 属性来转换 <code>Dataset</code> 为 <code>RDD</code></p>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>Dataset</code> 是一个新的 <code>Spark</code> 组件, 其底层还是 <code>RDD</code></p>
</li>
<li>
<p><code>Dataset</code> 提供了访问对象中某个特定字段的能力, 不用像 <code>RDD</code> 一样每次都要针对整个对象做操作</p>
</li>
<li>
<p><code>Dataset</code> 和 <code>RDD</code> 不同, 如果想把 <code>Dataset[T]</code> 转为 <code>RDD[T]</code>, 则需要对 <code>Dataset</code> 底层的 <code>InternalRow</code> 做转换, 是一个比价重量级的操作</p>
</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_5_dataframe_的作用和常见操作">5. DataFrame 的作用和常见操作</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>DataFrame</code> 是什么</p>
</li>
<li>
<p>理解 <code>DataFrame</code> 的常见操作</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>DataFrame</code> 是什么?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>DataFrame</code> 是 <code>SparkSQL</code> 中一个表示关系型数据库中 <code>表</code> 的函数式抽象, 其作用是让 <code>Spark</code> 处理大规模结构化数据的时候更加容易. 一般 <code>DataFrame</code> 可以处理结构化的数据, 或者是半结构化的数据, 因为这两类数据中都可以获取到 <code>Schema</code> 信息. 也就是说 <code>DataFrame</code> 中有 <code>Schema</code> 信息, 可以像操作表一样操作 <code>DataFrame</code>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/eca0d2e1e2b5ce678161438d87707b61.png" alt="eca0d2e1e2b5ce678161438d87707b61">
</div>
</div>
<div class="paragraph">
<p><code>DataFrame</code> 由两部分构成, 一是 <code>row</code> 的集合, 每个 <code>row</code> 对象表示一个行, 二是描述 <code>DataFrame</code> 结构的 <code>Schema</code>.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/238c241593cd5b0fd06d4d74294680e2.png" alt="238c241593cd5b0fd06d4d74294680e2">
</div>
</div>
<div class="paragraph">
<p><code>DataFrame</code> 支持 <code>SQL</code> 中常见的操作, 例如: <code>select</code>, <code>filter</code>, <code>join</code>, <code>group</code>, <code>sort</code>, <code>join</code> 等</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val peopleDF: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()

/*
+---+-----+
|age|count|
+---+-----+
| 15|    2|
+---+-----+
 */
peopleDF.groupBy('age)
  .count()
  .show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">通过隐式转换创建 <code>DataFrame</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>这种方式本质上是使用 <code>SparkSession</code> 中的隐式转换来进行的</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

// 必须要导入隐式转换
// 注意: spark 在此处不是包, 而是 SparkSession 对象
import spark.implicits._

val peopleDF: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()</code></pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/841503b4240e7a8ecac62d92203e9943.png" alt="841503b4240e7a8ecac62d92203e9943" width="600">
</div>
</div>
<div class="paragraph">
<p>根据源码可以知道, <code>toDF</code> 方法可以在 <code>RDD</code> 和 <code>Seq</code> 中使用</p>
</div>
<div class="paragraph">
<p>通过集合创建 <code>DataFrame</code> 的时候, 集合中不仅可以包含样例类, 也可以只有普通数据类型, 后通过指定列名来创建</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val df1: DataFrame = Seq("nihao", "hello").toDF("text")

/*
+-----+
| text|
+-----+
|nihao|
|hello|
+-----+
 */
df1.show()

val df2: DataFrame = Seq(("a", 1), ("b", 1)).toDF("word", "count")

/*
+----+-----+
|word|count|
+----+-----+
|   a|    1|
|   b|    1|
+----+-----+
 */
df2.show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">通过外部集合创建 <code>DataFrame</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

val df = spark.read
  .option("header", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")
df.show(10)
df.printSchema()</code></pre>
</div>
</div>
<div class="paragraph">
<p>不仅可以从 <code>csv</code> 文件创建 <code>DataFrame</code>, 还可以从 <code>Table</code>, <code>JSON</code>, <code>Parquet</code> 等中创建 <code>DataFrame</code>, 后续会有单独的章节来介绍</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">在 <code>DataFrame</code> 上可以使用的常规操作</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p>需求: 查看每个月的统计数量</p>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">Step 1: 首先可以打印 <code>DataFrame</code> 的 <code>Schema</code>, 查看其中所包含的列, 以及列的类型</dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

val df = spark.read
  .option("header", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")

df.printSchema()</code></pre>
</div>
</div>
</dd>
<dt class="hdlist1">Step 2: 对于大部分计算来说, 可能不会使用所有的列, 所以可以选择其中某些重要的列</dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">...

df.select('year, 'month, 'PM_Dongsi)</code></pre>
</div>
</div>
</dd>
<dt class="hdlist1">Step 3: 可以针对某些列进行分组, 后对每组数据通过函数做聚合</dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">...

df.select('year, 'month, 'PM_Dongsi)
  .where('PM_Dongsi =!= "Na")
  .groupBy('year, 'month)
  .count()
  .show()</code></pre>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">使用 <code>SQL</code> 操作 <code>DataFrame</code></dt>
</dl>
</div>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>使用 <code>SQL</code> 来操作某个 <code>DataFrame</code> 的话, <code>SQL</code> 中必须要有一个 <code>from</code> 子句, 所以需要先将 <code>DataFrame</code> 注册为一张临时表</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

val df = spark.read
  .option("header", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")

df.createOrReplaceTempView("temp_table")

spark.sql("select year, month, count(*) from temp_table where PM_Dongsi != 'NA' group by year, month")
  .show()</code></pre>
</div>
</div>
</div>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>DataFrame</code> 是一个类似于关系型数据库表的函数式组件</p>
</li>
<li>
<p><code>DataFrame</code> 一般处理结构化数据和半结构化数据</p>
</li>
<li>
<p><code>DataFrame</code> 具有数据对象的 Schema 信息</p>
</li>
<li>
<p>可以使用命令式的 <code>API</code> 操作 <code>DataFrame</code>, 同时也可以使用 <code>SQL</code> 操作 <code>DataFrame</code></p>
</li>
<li>
<p><code>DataFrame</code> 可以由一个已经存在的集合直接创建, 也可以读取外部的数据源来创建</p>
</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_6_dataset_和_dataframe_的异同">6. Dataset 和 DataFrame 的异同</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>Dataset</code> 和 <code>DataFrame</code> 之间的关系</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1"><code>DataFrame</code> 就是 <code>Dataset</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>根据前面的内容, 可以得到如下信息</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>Dataset</code> 中可以使用列来访问数据, <code>DataFrame</code> 也可以</p>
</li>
<li>
<p><code>Dataset</code> 的执行是优化的, <code>DataFrame</code> 也是</p>
</li>
<li>
<p><code>Dataset</code> 具有命令式 <code>API</code>, 同时也可以使用 <code>SQL</code> 来访问, <code>DataFrame</code> 也可以使用这两种不同的方式访问</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>所以这件事就比较蹊跷了, 两个这么相近的东西为什么会同时出现在 <code>SparkSQL</code> 中呢?</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/44fb917304a91eab99d131010448331b.png" alt="44fb917304a91eab99d131010448331b" width="600">
</div>
</div>
<div class="paragraph">
<p>确实, 这两个组件是同一个东西, <code>DataFrame</code> 是 <code>Dataset</code> 的一种特殊情况, 也就是说 <code>DataFrame</code> 是 <code>Dataset[Row]</code> 的别名</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>DataFrame</code> 和 <code>Dataset</code> 所表达的语义不同</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><strong>第一点: <code>DataFrame</code> 表达的含义是一个支持函数式操作的 <code>表</code>, 而 <code>Dataset</code> 表达是是一个类似 <code>RDD</code> 的东西, <code>Dataset</code> 可以处理任何对象</strong></p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">第二点: <code>DataFrame</code> 中所存放的是 <code>Row</code> 对象, 而 <code>Dataset</code> 中可以存放任何类型的对象</dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val df: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()       <i class="conum" data-value="1"></i><b>(1)</b>

val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS() <i class="conum" data-value="2"></i><b>(2)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>DataFrame 就是 Dataset[Row]</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Dataset 的范型可以是任意类型</td>
</tr>
</table>
</div>
</dd>
<dt class="hdlist1">第三点: <code>DataFrame</code> 的操作方式和 <code>Dataset</code> 是一样的, 但是对于强类型操作而言, 它们处理的类型不同</dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p><code>DataFrame</code> 在进行强类型操作时候, 例如 <code>map</code> 算子, 其所处理的数据类型永远是 <code>Row</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">df.map( (row: Row) =&gt; Row(row.get(0), row.getAs[Int](1) * 10) )(RowEncoder.apply(df.schema)).show()</code></pre>
</div>
</div>
<div class="paragraph">
<p>但是对于 <code>Dataset</code> 来讲, 其中是什么类型, 它就处理什么类型</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">ds.map( (item: People) =&gt; People(item.name, item.age * 10) ).show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">第三点: <code>DataFrame</code> 只能做到运行时类型检查, <code>Dataset</code> 能做到编译和运行时都有类型检查</dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>DataFrame</code> 中存放的数据以 <code>Row</code> 表示, 一个 <code>Row</code> 代表一行数据, 这和关系型数据库类似</p>
</li>
<li>
<p><code>DataFrame</code> 在进行 <code>map</code> 等操作的时候, <code>DataFrame</code> 不能直接使用 <code>Person</code> 这样的 <code>Scala</code> 对象, 所以无法做到编译时检查</p>
</li>
<li>
<p><code>Dataset</code> 表示的具体的某一类对象, 例如 <code>Person</code>, 所以再进行 <code>map</code> 等操作的时候, 传入的是具体的某个 <code>Scala</code> 对象, 如果调用错了方法, 编译时就会被检查出来</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
ds.map(person =&gt; person.hello) <i class="conum" data-value="1"></i><b>(1)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>这行代码明显报错, 无法通过编译</td>
</tr>
</table>
</div>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Row</code> 是什么?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>Row</code> 对象表示的是一个 <code>行</code></p>
</div>
<div class="paragraph">
<p><code>Row</code> 的操作类似于 <code>Scala</code> 中的 <code>Map</code> 数据类型</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">// 一个对象就是一个对象
val p = People(name = "zhangsan", age = 10)

// 同样一个对象, 还可以通过一个 Row 对象来表示
val row = Row("zhangsan", 10)

// 获取 Row 中的内容
println(row.get(1))
println(row(1))

// 获取时可以指定类型
println(row.getAs[Int](1))

// 同时 Row 也是一个样例类, 可以进行 match
row match {
  case Row(name, age) =&gt; println(name, age)
}</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>DataFrame</code> 和 <code>Dataset</code> 之间可以非常简单的相互转换</dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

import spark.implicits._

val df: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()
val ds_fdf: Dataset[People] = df.as[People]

val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
val df_fds: DataFrame = ds.toDF()</code></pre>
</div>
</div>
</dd>
</dl>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>DataFrame</code> 就是 <code>Dataset</code>, 他们的方式是一样的, 也都支持 <code>API</code> 和 <code>SQL</code> 两种操作方式</p>
</li>
<li>
<p><code>DataFrame</code> 只能通过表达式的形式, 或者列的形式来访问数据, 只有 <code>Dataset</code> 支持针对于整个对象的操作</p>
</li>
<li>
<p><code>DataFrame</code> 中的数据表示为 <code>Row</code>, 是一个行的概念</p>
</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_7_数据读写">7. 数据读写</h2>
<div class="sectionbody">
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解外部数据源的访问框架</p>
</li>
<li>
<p>掌握常见的数据源读写方式</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_1_初识_dataframereader">7.1. 初识 DataFrameReader</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="ulist">
<ul>
<li>
<p>理解 <code>DataFrameReader</code> 的整体结构和组成</p>
</li>
</ul>
</div>
</div>
</div>
<div class="paragraph">
<p><code>SparkSQL</code> 的一个非常重要的目标就是完善数据读取, 所以 <code>SparkSQL</code> 中增加了一个新的框架, 专门用于读取外部数据源, 叫做 <code>DataFrameReader</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrameReader

val spark: SparkSession = ...

val reader: DataFrameReader = spark.read</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>DataFrameReader</code> 由如下几个组件组成</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">组件</th>
<th class="tableblock halign-left valign-top">解释</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>schema</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">结构信息, 因为 <code>Dataset</code> 是有结构的, 所以在读取数据的时候, 就需要有 <code>Schema</code> 信息, 有可能是从外部数据源获取的, 也有可能是指定的</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>option</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">连接外部数据源的参数, 例如 <code>JDBC</code> 的 <code>URL</code>, 或者读取 <code>CSV</code> 文件是否引入 <code>Header</code> 等</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>format</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">外部数据源的格式, 例如 <code>csv</code>, <code>jdbc</code>, <code>json</code> 等</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p><code>DataFrameReader</code> 有两种访问方式, 一种是使用 <code>load</code> 方法加载, 使用 <code>format</code> 指定加载格式, 还有一种是使用封装方法, 类似 <code>csv</code>, <code>json</code>, <code>jdbc</code> 等</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrame

val spark: SparkSession = ...

// 使用 load 方法
val fromLoad: DataFrame = spark
  .read
  .format("csv")
  .option("header", true)
  .option("inferSchema", true)
  .load("dataset/BeijingPM20100101_20151231.csv")

// Using format-specific load operator
val fromCSV: DataFrame = spark
  .read
  .option("header", true)
  .option("inferSchema", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")</code></pre>
</div>
</div>
<div class="paragraph">
<p>但是其实这两种方式本质上一样, 因为类似 <code>csv</code> 这样的方式只是 <code>load</code> 的封装</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/e8af7d7e5ec256de27b2e40c8449a906.png" alt="e8af7d7e5ec256de27b2e40c8449a906">
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>如果使用 <code>load</code> 方法加载数据, 但是没有指定 <code>format</code> 的话, 默认是按照 <code>Parquet</code> 文件格式读取</p>
</div>
<div class="paragraph">
<p>也就是说, <code>SparkSQL</code> 默认的读取格式是 <code>Parquet</code></p>
</div>
</td>
</tr>
</table>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>使用 <code>spark.read</code> 可以获取 SparkSQL 中的外部数据源访问框架 <code>DataFrameReader</code></p>
</li>
<li>
<p><code>DataFrameReader</code> 有三个组件 <code>format</code>, <code>schema</code>, <code>option</code></p>
</li>
<li>
<p><code>DataFrameReader</code> 有两种使用方式, 一种是使用 <code>load</code> 加 <code>format</code> 指定格式, 还有一种是使用封装方法 <code>csv</code>, <code>json</code> 等</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_2_初识_dataframewriter">7.2. 初识 DataFrameWriter</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>DataFrameWriter</code> 的结构</p>
</li>
</ol>
</div>
</div>
</div>
<div class="paragraph">
<p>对于 <code>ETL</code> 来说, 数据保存和数据读取一样重要, 所以 <code>SparkSQL</code> 中增加了一个新的数据写入框架, 叫做 <code>DataFrameWriter</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = ...

val df = spark.read
      .option("header", true)
      .csv("dataset/BeijingPM20100101_20151231.csv")

val writer: DataFrameWriter[Row] = df.write</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>DataFrameWriter</code> 中由如下几个部分组成</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">组件</th>
<th class="tableblock halign-left valign-top">解释</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>source</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">写入目标, 文件格式等, 通过 <code>format</code> 方法设定</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>mode</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">写入模式, 例如一张表已经存在, 如果通过 <code>DataFrameWriter</code> 向这张表中写入数据, 是覆盖表呢, 还是向表中追加呢? 通过 <code>mode</code> 方法设定</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>extraOptions</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">外部参数, 例如 <code>JDBC</code> 的 <code>URL</code>, 通过 <code>options</code>, <code>option</code> 设定</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>partitioningColumns</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">类似 <code>Hive</code> 的分区, 保存表的时候使用, 这个地方的分区不是 <code>RDD</code> 的分区, 而是文件的分区, 或者表的分区, 通过 <code>partitionBy</code> 设定</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>bucketColumnNames</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">类似 <code>Hive</code> 的分桶, 保存表的时候使用, 通过 <code>bucketBy</code> 设定</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>sortColumnNames</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">用于排序的列, 通过 <code>sortBy</code> 设定</p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p><code>mode</code> 指定了写入模式, 例如覆盖原数据集, 或者向原数据集合中尾部添加等</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top"><code>Scala</code> 对象表示</th>
<th class="tableblock halign-left valign-top">字符串表示</th>
<th class="tableblock halign-left valign-top">解释</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SaveMode.ErrorIfExists</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"error"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">将 <code>DataFrame</code> 保存到 <code>source</code> 时, 如果目标已经存在, 则报错</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SaveMode.Append</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"append"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">将 <code>DataFrame</code> 保存到 <code>source</code> 时, 如果目标已经存在, 则添加到文件或者 <code>Table</code> 中</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SaveMode.Overwrite</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"overwrite"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">将 <code>DataFrame</code> 保存到 <code>source</code> 时, 如果目标已经存在, 则使用 <code>DataFrame</code> 中的数据完全覆盖目标</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SaveMode.Ignore</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>"ignore"</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">将 <code>DataFrame</code> 保存到 <code>source</code> 时, 如果目标已经存在, 则不会保存 <code>DataFrame</code> 数据, 并且也不修改目标数据集, 类似于 <code>CREATE TABLE IF NOT EXISTS</code></p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p><code>DataFrameWriter</code> 也有两种使用方式, 一种是使用 <code>format</code> 配合 <code>save</code>, 还有一种是使用封装方法, 例如 <code>csv</code>, <code>json</code>, <code>saveAsTable</code> 等</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = ...

val df = spark.read
  .option("header", true)
  .csv("dataset/BeijingPM20100101_20151231.csv")

// 使用 save 保存, 使用 format 设置文件格式
df.write.format("json").save("dataset/beijingPM")

// 使用 json 保存, 因为方法是 json, 所以隐含的 format 是 json
df.write.json("dataset/beijingPM1")</code></pre>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>默认没有指定 <code>format</code>, 默认的 <code>format</code> 是 <code>Parquet</code></p>
</div>
</td>
</tr>
</table>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>类似 <code>DataFrameReader</code>, <code>Writer</code> 中也有 <code>format</code>, <code>options</code>, 另外 <code>schema</code> 是包含在 <code>DataFrame</code> 中的</p>
</li>
<li>
<p><code>DataFrameWriter</code> 中还有一个很重要的概念叫做 <code>mode</code>, 指定写入模式, 如果目标集合已经存在时的行为</p>
</li>
<li>
<p><code>DataFrameWriter</code> 可以将数据保存到 <code>Hive</code> 表中, 所以也可以指定分区和分桶信息</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_3_读写_parquet_格式文件">7.3. 读写 Parquet 格式文件</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>Spark</code> 读写 <code>Parquet</code> 文件的语法</p>
</li>
<li>
<p>理解 <code>Spark</code> 读写 <code>Parquet</code> 文件的时候对于分区的处理</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">什么时候会用到 <code>Parquet</code> ?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/00a2a56f725d86b5c27463f109c43d8c.png" alt="00a2a56f725d86b5c27463f109c43d8c">
</div>
</div>
<div class="paragraph">
<p>在 <code>ETL</code> 中, <code>Spark</code> 经常扮演 <code>T</code> 的职务, 也就是进行数据清洗和数据转换.</p>
</div>
<div class="paragraph">
<p>为了能够保存比较复杂的数据, 并且保证性能和压缩率, 通常使用 <code>Parquet</code> 是一个比较不错的选择.</p>
</div>
<div class="paragraph">
<p>所以外部系统收集过来的数据, 有可能会使用 <code>Parquet</code>, 而 <code>Spark</code> 进行读取和转换的时候, 就需要支持对 <code>Parquet</code> 格式的文件的支持.</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">使用代码读写 <code>Parquet</code> 文件</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>默认不指定 <code>format</code> 的时候, 默认就是读写 <code>Parquet</code> 格式的文件</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

val df = spark.read
  .option("header", value = true)
  .csv("dataset/911.csv")

// 保存 Parquet 文件
df.write.mode("override").save("dataset/911.parquet")

// 读取 Parquet 文件
val dfFromParquet = spark.read.parquet("dataset/911.parquet")
dfFromParquet.createOrReplaceTempView("911")

spark.sql("select * from 911 where zip &gt; 19000 and zip &lt; 19400").show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">写入 <code>Parquet</code> 的时候可以指定分区</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>Spark</code> 在写入文件的时候是支持分区的, 可以像 <code>Hive</code> 一样设置某个列为分区列</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

// 从 CSV 中读取内容
val dfFromParquet = spark.read.option("header", value = true).csv("dataset/BeijingPM20100101_20151231.csv")

// 保存为 Parquet 格式文件, 不指定 format 默认就是 Parquet
dfFromParquet.write.partitionBy("year", "month").save("dataset/beijing_pm")</code></pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/67314102d7b36b791b04bafeb5d0d3e8.png" alt="67314102d7b36b791b04bafeb5d0d3e8" width="300">
</div>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>这个地方指的分区是类似 <code>Hive</code> 中表分区的概念, 而不是 <code>RDD</code> 分布式分区的含义</p>
</div>
</td>
</tr>
</table>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">分区发现</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>在读取常见文件格式的时候, <code>Spark</code> 会自动的进行分区发现, 分区自动发现的时候, 会将文件名中的分区信息当作一列. 例如 如果按照性别分区, 那么一般会生成两个文件夹 <code>gender=male</code> 和 <code>gender=female</code>, 那么在使用 <code>Spark</code> 读取的时候, 会自动发现这个分区信息, 并且当作列放入创建的 <code>DataFrame</code> 中</p>
</div>
<div class="paragraph">
<p>使用代码证明这件事可以有两个步骤, 第一步先读取某个分区的单独一个文件并打印其 <code>Schema</code> 信息, 第二步读取整个数据集所有分区并打印 <code>Schema</code> 信息, 和第一步做比较就可以确定</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark = ...

val partDF = spark.read.load("dataset/beijing_pm/year=2010/month=1") <i class="conum" data-value="1"></i><b>(1)</b>
partDF.printSchema()</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>把分区的数据集中的某一个区单做一整个数据集读取, 没有分区信息, 自然也不会进行分区发现</td>
</tr>
</table>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/dbb274b7fcdfd82c3a3922dfa6bfb29e.png" alt="dbb274b7fcdfd82c3a3922dfa6bfb29e" width="600">
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val df = spark.read.load("dataset/beijing_pm") <i class="conum" data-value="1"></i><b>(1)</b>
df.printSchema()</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>此处读取的是整个数据集, 会进行分区发现, DataFrame 中会包含分去列</td>
</tr>
</table>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/84353e6ed2cf479b82b4d2e4e2b6c3c2.png" alt="84353e6ed2cf479b82b4d2e4e2b6c3c2" width="600">
</div>
</div>
</div>
</div>
</dd>
</dl>
</div>
<table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 1. <code>SparkSession</code> 中有关 <code>Parquet</code> 的配置</caption>
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">配置</th>
<th class="tableblock halign-left valign-top">默认值</th>
<th class="tableblock halign-left valign-top">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.parquet.binaryAsString</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>false</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">一些其他 <code>Parquet</code> 生产系统, 不区分字符串类型和二进制类型, 该配置告诉 <code>SparkSQL</code> 将二进制数据解释为字符串以提供与这些系统的兼容性</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.parquet.int96AsTimestamp</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>true</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">一些其他 <code>Parquet</code> 生产系统, 将 <code>Timestamp</code> 存为 <code>INT96</code>, 该配置告诉 <code>SparkSQL</code> 将 <code>INT96</code> 解析为 <code>Timestamp</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.parquet.cacheMetadata</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>true</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">打开 Parquet 元数据的缓存, 可以加快查询静态数据</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.parquet.compression.codec</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>snappy</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">压缩方式, 可选 <code>uncompressed</code>, <code>snappy</code>, <code>gzip</code>, <code>lzo</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.parquet.mergeSchema</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>false</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">当为 true 时, Parquet 数据源会合并从所有数据文件收集的 Schemas 和数据, 因为这个操作开销比较大, 所以默认关闭</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>spark.sql.optimizer.metadataOnly</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>true</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">如果为 <code>true</code>, 会通过原信息来生成分区列, 如果为 <code>false</code> 则就是通过扫描整个数据集来确定</p></td>
</tr>
</tbody>
</table>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>Spark</code> 不指定 <code>format</code> 的时候默认就是按照 <code>Parquet</code> 的格式解析文件</p>
</li>
<li>
<p><code>Spark</code> 在读取 <code>Parquet</code> 文件的时候会自动的发现 <code>Parquet</code> 的分区和分区字段</p>
</li>
<li>
<p><code>Spark</code> 在写入 <code>Parquet</code> 文件的时候如果设置了分区字段, 会自动的按照分区存储</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_4_读写_json_格式文件">7.4. 读写 JSON 格式文件</h3>
<div class="exampleblock">
<div class="title">目标</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>理解 <code>JSON</code> 的使用场景</p>
</li>
<li>
<p>能够使用 <code>Spark</code> 读取处理 <code>JSON</code> 格式文件</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">什么时候会用到 <code>JSON</code> ?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/00a2a56f725d86b5c27463f109c43d8c.png" alt="00a2a56f725d86b5c27463f109c43d8c">
</div>
</div>
<div class="paragraph">
<p>在 <code>ETL</code> 中, <code>Spark</code> 经常扮演 <code>T</code> 的职务, 也就是进行数据清洗和数据转换.</p>
</div>
<div class="paragraph">
<p>在业务系统中, <code>JSON</code> 是一个非常常见的数据格式, 在前后端交互的时候也往往会使用 <code>JSON</code>, 所以从业务系统获取的数据很大可能性是使用 <code>JSON</code> 格式, 所以就需要 <code>Spark</code> 能够支持 JSON 格式文件的读取</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">读写 <code>JSON</code> 文件</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>将要 <code>Dataset</code> 保存为 <code>JSON</code> 格式的文件比较简单, 是 <code>DataFrameWriter</code> 的一个常规使用</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = new sql.SparkSession.Builder()
  .appName("hello")
  .master("local[6]")
  .getOrCreate()

val dfFromParquet = spark.read.load("dataset/beijing_pm")

// 将 DataFrame 保存为 JSON 格式的文件
dfFromParquet.repartition(1)        <i class="conum" data-value="1"></i><b>(1)</b>
  .write.format("json")
  .save("dataset/beijing_pm_json")</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>如果不重新分区, 则会为 <code>DataFrame</code> 底层的 <code>RDD</code> 的每个分区生成一个文件, 为了保持只有一个输出文件, 所以重新分区</td>
</tr>
</table>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>保存为 <code>JSON</code> 格式的文件有一个细节需要注意, 这个 <code>JSON</code> 格式的文件中, 每一行是一个独立的 <code>JSON</code>, 但是整个文件并不只是一个 <code>JSON</code> 字符串, 所以这种文件格式很多时候被成为 <code>JSON Line</code> 文件, 有时候后缀名也会变为 <code>jsonl</code></p>
</div>
<div class="listingblock">
<div class="title">beijing_pm.jsonl</div>
<div class="content">
<pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">{"day":"1","hour":"0","season":"1","year":2013,"month":3}
{"day":"1","hour":"1","season":"1","year":2013,"month":3}
{"day":"1","hour":"2","season":"1","year":2013,"month":3}</code></pre>
</div>
</div>
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>也可以通过 <code>DataFrameReader</code> 读取一个 <code>JSON Line</code> 文件</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = ...

val dfFromJSON = spark.read.json("dataset/beijing_pm_json")
dfFromJSON.show()</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>JSON</code> 格式的文件是有结构信息的, 也就是 <code>JSON</code> 中的字段是有类型的, 例如 <code>"name": "zhangsan"</code> 这样由双引号包裹的 <code>Value</code>, 就是字符串类型, 而 <code>"age": 10</code> 这种没有双引号包裹的就是数字类型, 当然, 也可以是布尔型 <code>"has_wife": true</code></p>
</div>
<div class="paragraph">
<p><code>Spark</code> 读取 <code>JSON Line</code> 文件的时候, 会自动的推断类型信息</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = ...

val dfFromJSON = spark.read.json("dataset/beijing_pm_json")

dfFromJSON.printSchema()</code></pre>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/e8a53ef37bbf6675525d1a844f8648f1.png" alt="e8a53ef37bbf6675525d1a844f8648f1" width="600">
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Spark</code> 可以从一个保存了 <code>JSON</code> 格式字符串的 <code>Dataset[String]</code> 中读取 <code>JSON</code> 信息, 转为 <code>DataFrame</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>这种情况其实还是比较常见的, 例如如下的流程</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/da6f1c7f8d98691117a173e03bfdf18f.png" alt="da6f1c7f8d98691117a173e03bfdf18f">
</div>
</div>
<div class="paragraph">
<p>假设业务系统通过 <code>Kafka</code> 将数据流转进入大数据平台, 这个时候可能需要使用 <code>RDD</code> 或者 <code>Dataset</code> 来读取其中的内容, 这个时候一条数据就是一个 <code>JSON</code> 格式的字符串, 如何将其转为 <code>DataFrame</code> 或者 <code>Dataset[Object]</code> 这样具有 <code>Schema</code> 的数据集呢? 使用如下代码就可以</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark: SparkSession = ...

import spark.implicits._

val peopleDataset = spark.createDataset(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)

spark.read.json(peopleDataset).show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="exampleblock">
<div class="title">总结</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><code>JSON</code> 通常用于系统间的交互, <code>Spark</code> 经常要读取 <code>JSON</code> 格式文件, 处理, 放在另外一处</p>
</li>
<li>
<p>使用 <code>DataFrameReader</code> 和 <code>DataFrameWriter</code> 可以轻易的读取和写入 <code>JSON</code>, 并且会自动处理数据类型信息</p>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_5_访问_hive">7.5. 访问 Hive</h3>
<div class="exampleblock">
<div class="title">导读</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>整合 <code>SparkSQL</code> 和 <code>Hive</code>, 使用 <code>Hive</code> 的 <code>MetaStore</code> 元信息库</p>
</li>
<li>
<p>使用 <code>SparkSQL</code> 查询 <code>Hive</code> 表</p>
</li>
<li>
<p>案例, 使用常见 <code>HiveSQL</code></p>
</li>
<li>
<p>写入内容到 <code>Hive</code> 表</p>
</li>
</ol>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_7_5_1_sparksql_整合_hive">7.5.1. SparkSQL 整合 Hive</h4>
<div class="exampleblock">
<div class="title">导读</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>开启 <code>Hive</code> 的 <code>MetaStore</code> 独立进程</p>
</li>
<li>
<p>整合 <code>SparkSQL</code> 和 <code>Hive</code> 的 <code>MetaStore</code></p>
</li>
</ol>
</div>
</div>
</div>
<div class="paragraph">
<p>和一个文件格式不同, <code>Hive</code> 是一个外部的数据存储和查询引擎, 所以如果 <code>Spark</code> 要访问 <code>Hive</code> 的话, 就需要先整合 <code>Hive</code></p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">整合什么 ?</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>如果要讨论 <code>SparkSQL</code> 如何和 <code>Hive</code> 进行整合, 首要考虑的事应该是 <code>Hive</code> 有什么, 有什么就整合什么就可以</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>MetaStore</code>, 元数据存储</p>
<div class="paragraph">
<p><code>SparkSQL</code> 内置的有一个 <code>MetaStore</code>, 通过嵌入式数据库 <code>Derby</code> 保存元信息, 但是对于生产环境来说, 还是应该使用 <code>Hive</code> 的 <code>MetaStore</code>, 一是更成熟, 功能更强, 二是可以使用 <code>Hive</code> 的元信息</p>
</div>
</li>
<li>
<p>查询引擎</p>
<div class="paragraph">
<p><code>SparkSQL</code> 内置了 <code>HiveSQL</code> 的支持, 所以无需整合</p>
</div>
</li>
</ul>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">为什么要开启 <code>Hive</code> 的 <code>MetaStore</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><code>Hive</code> 的 <code>MetaStore</code> 是一个 <code>Hive</code> 的组件, 一个 <code>Hive</code> 提供的程序, 用以保存和访问表的元数据, 整个 <code>Hive</code> 的结构大致如下</p>
</div>
<div class="imageblock">
<div class="content">
<img src="https://doc-1256053707.cos.ap-beijing.myqcloud.com/20190523011946.png" alt="20190523011946">
</div>
</div>
<div class="paragraph">
<p>由上图可知道, 其实 <code>Hive</code> 中主要的组件就三个, <code>HiveServer2</code> 负责接受外部系统的查询请求, 例如 <code>JDBC</code>, <code>HiveServer2</code> 接收到查询请求后, 交给 <code>Driver</code> 处理, <code>Driver</code> 会首先去询问 <code>MetaStore</code> 表在哪存, 后 <code>Driver</code> 程序通过 <code>MR</code> 程序来访问 <code>HDFS</code> 从而获取结果返回给查询请求者</p>
</div>
<div class="paragraph">
<p>而 <code>Hive</code> 的 <code>MetaStore</code> 对 <code>SparkSQL</code> 的意义非常重大, 如果 <code>SparkSQL</code> 可以直接访问 <code>Hive</code> 的 <code>MetaStore</code>, 则理论上可以做到和 <code>Hive</code> 一样的事情, 例如通过 <code>Hive</code> 表查询数据</p>
</div>
<div class="paragraph">
<p>而 Hive 的 MetaStore 的运行模式有三种</p>
</div>
<div class="ulist">
<ul>
<li>
<p>内嵌 <code>Derby</code> 数据库模式</p>
<div class="paragraph">
<p>这种模式不必说了, 自然是在测试的时候使用, 生产环境不太可能使用嵌入式数据库, 一是不稳定, 二是这个 <code>Derby</code> 是单连接的, 不支持并发</p>
</div>
</li>
<li>
<p><code>Local</code> 模式</p>
<div class="paragraph">
<p><code>Local</code> 和 <code>Remote</code> 都是访问 <code>MySQL</code> 数据库作为存储元数据的地方, 但是 <code>Local</code> 模式的 <code>MetaStore</code> 没有独立进程, 依附于 <code>HiveServer2</code> 的进程</p>
</div>
</li>
<li>
<p><code>Remote</code> 模式</p>
<div class="paragraph">
<p>和 <code>Loca</code> 模式一样, 访问 <code>MySQL</code> 数据库存放元数据, 但是 <code>Remote</code> 的 <code>MetaStore</code> 运行在独立的进程中</p>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>我们显然要选择 <code>Remote</code> 模式, 因为要让其独立运行, 这样才能让 <code>SparkSQL</code> 一直可以访问</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Hive</code> 开启 <code>MetaStore</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="dlist">
<dl>
<dt class="hdlist1"><code>Step 1</code>: 修改 <code>hive-site.xml</code></dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-xml hljs" data-lang="xml">&lt;property&gt;
  &lt;name&gt;hive.metastore.warehouse.dir&lt;/name&gt;
  &lt;value&gt;/user/hive/warehouse&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;javax.jdo.option.ConnectionURL&lt;/name&gt;
  &lt;value&gt;jdbc:mysql://node01:3306/hive?createDatabaseIfNotExist=true&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;javax.jdo.option.ConnectionDriverName&lt;/name&gt;
  &lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;javax.jdo.option.ConnectionUserName&lt;/name&gt;
  &lt;value&gt;username&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;javax.jdo.option.ConnectionPassword&lt;/name&gt;
  &lt;value&gt;password&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;hive.metastore.local&lt;/name&gt;
  &lt;value&gt;false&lt;/value&gt;
&lt;/property&gt;

&lt;property&gt;
  &lt;name&gt;hive.metastore.uris&lt;/name&gt;
  &lt;value&gt;thrift://node01:9083&lt;/value&gt;  //当前服务器
&lt;/property&gt;</code></pre>
</div>
</div>
</dd>
<dt class="hdlist1"><code>Step 2</code>: 启动 <code>Hive MetaStore</code></dt>
<dd>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">nohup /export/servers/hive/bin/hive --service metastore 2&gt;&amp;1 &gt;&gt; /var/log.log &amp;</code></pre>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
</dd>
<dt class="hdlist1"><code>SparkSQL</code> 整合 <code>Hive</code> 的 <code>MetaStore</code></dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>即使不去整合 <code>MetaStore</code>, <code>Spark</code> 也有一个内置的 <code>MateStore</code>, 使用 <code>Derby</code> 嵌入式数据库保存数据, 但是这种方式不适合生产环境, 因为这种模式同一时间只能有一个 <code>SparkSession</code> 使用, 所以生产环境更推荐使用 <code>Hive</code> 的 <code>MetaStore</code></p>
</div>
<div class="paragraph">
<p><code>SparkSQL</code> 整合 <code>Hive</code> 的 <code>MetaStore</code> 主要思路就是要通过配置能够访问它, 并且能够使用 <code>HDFS</code> 保存 <code>WareHouse</code>, 这些配置信息一般存在于 <code>Hadoop</code> 和 <code>HDFS</code> 的配置文件中, 所以可以直接拷贝 <code>Hadoop</code> 和 <code>Hive</code> 的配置文件到 <code>Spark</code> 的配置目录</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">cd /export/servers/hadoop/etc/hadoop
cp hive-site.xml core-site.xml hdfs-site.xml /export/servers/spark/conf/ <i class="conum" data-value="1"></i><b>(1)</b> <i class="conum" data-value="2"></i><b>(2)</b> <i class="conum" data-value="3"></i><b>(3)</b>

scp -r /export/servers/spark/conf node02:/export/servers/spark/conf
scp -r /export/servers/spark/conf node03:/export/servers/spark/conf</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td><code>Spark</code> 需要 <code>hive-site.xml</code> 的原因是, 要读取 <code>Hive</code> 的配置信息, 主要是元数据仓库的位置等信息</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td><code>Spark</code> 需要 <code>core-site.xml</code> 的原因是, 要读取安全有关的配置</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td><code>Spark</code> 需要 <code>hdfs-site.xml</code> 的原因是, 有可能需要在 <code>HDFS</code> 中放置表文件, 所以需要 <code>HDFS</code> 的配置</td>
</tr>
</table>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>如果不希望通过拷贝文件的方式整合 Hive, 也可以在 SparkSession 启动的时候, 通过指定 Hive 的 MetaStore 的位置来访问, 但是更推荐整合的方式</p>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sect3">
<h4 id="_7_5_2_访问_hive_表">7.5.2. 访问 Hive 表</h4>
<div class="exampleblock">
<div class="title">导读</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>在 <code>Hive</code> 中创建表</p>
</li>
<li>
<p>使用 <code>SparkSQL</code> 访问 <code>Hive</code> 中已经存在的表</p>
</li>
<li>
<p>使用 <code>SparkSQL</code> 创建 <code>Hive</code> 表</p>
</li>
<li>
<p>使用 <code>SparkSQL</code> 修改 <code>Hive</code> 表中的数据</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">在 <code>Hive</code> 中创建表</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>第一步, 需要先将文件上传到集群中, 使用如下命令上传到 <code>HDFS</code> 中</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">hdfs dfs -mkdir -p /dataset
hdfs dfs -put studenttabl10k /dataset/</code></pre>
</div>
</div>
<div class="paragraph">
<p>第二步, 使用 <code>Hive</code> 或者 <code>Beeline</code> 执行如下 <code>SQL</code></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">CREATE DATABASE IF NOT EXISTS spark_integrition;

USE spark_integrition;

CREATE EXTERNAL TABLE student
(
  name  STRING,
  age   INT,
  gpa   string
)
ROW FORMAT DELIMITED
  FIELDS TERMINATED BY '\t'
  LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/dataset/hive';

LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student;</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">通过 <code>SparkSQL</code> 查询 <code>Hive</code> 的表</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>查询 <code>Hive</code> 中的表可以直接通过 <code>spark.sql(&#8230;&#8203;)</code> 来进行, 可以直接在其中访问 <code>Hive</code> 的 <code>MetaStore</code>, 前提是一定要将 <code>Hive</code> 的配置文件拷贝到 <code>Spark</code> 的 <code>conf</code> 目录</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">scala&gt; spark.sql("use spark_integrition")
scala&gt; val resultDF = spark.sql("select * from student limit 10")
scala&gt; resultDF.show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">通过 <code>SparkSQL</code> 创建 <code>Hive</code> 表</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>通过 <code>SparkSQL</code> 可以直接创建 <code>Hive</code> 表, 并且使用 <code>LOAD DATA</code> 加载数据</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val createTableStr =
  """
    |CREATE EXTERNAL TABLE student
    |(
    |  name  STRING,
    |  age   INT,
    |  gpa   string
    |)
    |ROW FORMAT DELIMITED
    |  FIELDS TERMINATED BY '\t'
    |  LINES TERMINATED BY '\n'
    |STORED AS TEXTFILE
    |LOCATION '/dataset/hive'
  """.stripMargin

spark.sql("CREATE DATABASE IF NOT EXISTS spark_integrition1")
spark.sql("USE spark_integrition1")
spark.sql(createTableStr)
spark.sql("LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student")
spark.sql("select * from student limit").show()</code></pre>
</div>
</div>
<div class="paragraph">
<p>目前 <code>SparkSQL</code> 支持的文件格式有 <code>sequencefile</code>, <code>rcfile</code>, <code>orc</code>, <code>parquet</code>, <code>textfile</code>, <code>avro</code>, 并且也可以指定 <code>serde</code> 的名称</p>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">使用 <code>SparkSQL</code> 处理数据并保存进 Hive 表</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>前面都在使用 <code>SparkShell</code> 的方式来访问 <code>Hive</code>, 编写 <code>SQL</code>, 通过 <code>Spark</code> 独立应用的形式也可以做到同样的事, 但是需要一些前置的步骤, 如下</p>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">Step 1: 导入 <code>Maven</code> 依赖</dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.spark&lt;/groupId&gt;
    &lt;artifactId&gt;spark-hive_2.11&lt;/artifactId&gt;
    &lt;version&gt;${spark.version}&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">Step 2: 配置 <code>SparkSession</code></dt>
<dd>
<div class="exampleblock">
<div class="content">
<div class="paragraph">
<p>如果希望使用 <code>SparkSQL</code> 访问 <code>Hive</code> 的话, 需要做两件事</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>开启 <code>SparkSession</code> 的 <code>Hive</code> 支持</p>
<div class="paragraph">
<p>经过这一步配置, <code>SparkSQL</code> 才会把 <code>SQL</code> 语句当作 <code>HiveSQL</code> 来进行解析</p>
</div>
</li>
<li>
<p>设置 <code>WareHouse</code> 的位置</p>
<div class="paragraph">
<p>虽然 <code>hive-stie.xml</code> 中已经配置了 <code>WareHouse</code> 的位置, 但是在 <code>Spark 2.0.0</code> 后已经废弃了 <code>hive-site.xml</code> 中设置的 <code>hive.metastore.warehouse.dir</code>, 需要在 <code>SparkSession</code> 中设置 <code>WareHouse</code> 的位置</p>
</div>
</li>
<li>
<p>设置 <code>MetaStore</code> 的位置</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark = SparkSession
  .builder()
  .appName("hive example")
  .config("spark.sql.warehouse.dir", "hdfs://node01:8020/dataset/hive")  <i class="conum" data-value="1"></i><b>(1)</b>
  .config("hive.metastore.uris", "thrift://node01:9083")                 <i class="conum" data-value="2"></i><b>(2)</b>
  .enableHiveSupport()                                                   <i class="conum" data-value="3"></i><b>(3)</b>
  .getOrCreate()</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>设置 <code>WareHouse</code> 的位置</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>设置 <code>MetaStore</code> 的位置</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>开启 <code>Hive</code> 支持</td>
</tr>
</table>
</div>
</div>
</div>
</dd>
</dl>
</div>
<div class="paragraph">
<p>配置好了以后, 就可以通过 <code>DataFrame</code> 处理数据, 后将数据结果推入 <code>Hive</code> 表中了, 在将结果保存到 <code>Hive</code> 表的时候, 可以指定保存模式</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val schema = StructType(
  List(
    StructField("name", StringType),
    StructField("age", IntegerType),
    StructField("gpa", FloatType)
  )
)

val studentDF = spark.read
  .option("delimiter", "\t")
  .schema(schema)
  .csv("dataset/studenttab10k")

val resultDF = studentDF.where("age &lt; 50")

resultDF.write.mode(SaveMode.Overwrite).saveAsTable("spark_integrition1.student") <i class="conum" data-value="1"></i><b>(1)</b></code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>通过 <code>mode</code> 指定保存模式, 通过 <code>saveAsTable</code> 保存数据到 <code>Hive</code></td>
</tr>
</table>
</div>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_7_6_jdbc">7.6. JDBC</h3>
<div class="exampleblock">
<div class="title">导读</div>
<div class="content">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>通过 <code>SQL</code> 操作 <code>MySQL</code> 的表</p>
</li>
<li>
<p>将数据写入 <code>MySQL</code> 的表中</p>
</li>
</ol>
</div>
</div>
</div>
<div class="dlist">
<dl>
<dt class="hdlist1">准备 <code>MySQL</code> 环境</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>在使用 <code>SparkSQL</code> 访问 <code>MySQL</code> 之前, 要对 <code>MySQL</code> 进行一些操作, 例如说创建用户, 表和库等</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Step 1: 连接 <code>MySQL</code> 数据库</p>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>在 <code>MySQL</code> 所在的主机上执行如下命令</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">mysql -u root -p</code></pre>
</div>
</div>
</div>
</div>
</li>
<li>
<p>Step 2: 创建 <code>Spark</code> 使用的用户</p>
<div class="openblock">
<div class="content">
<div class="paragraph">
<p>登进 <code>MySQL</code> 后, 需要先创建用户</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">CREATE USER 'spark'@'%' IDENTIFIED BY 'Spark123!';
GRANT ALL ON spark_test.* TO 'spark'@'%';</code></pre>
</div>
</div>
</div>
</div>
</li>
<li>
<p>Step 3: 创建库和表</p>
<div class="openblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-sql hljs" data-lang="sql">CREATE DATABASE spark_test;

USE spark_test;

CREATE TABLE IF NOT EXISTS `student`(
`id` INT AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`age` INT NOT NULL,
`gpa` FLOAT,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;</code></pre>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">使用 <code>SparkSQL</code> 向 <code>MySQL</code> 中写入数据</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>其实在使用 <code>SparkSQL</code> 访问 <code>MySQL</code> 是通过 <code>JDBC</code>, 那么其实所有支持 <code>JDBC</code> 的数据库理论上都可以通过这种方式进行访问</p>
</div>
<div class="paragraph">
<p>在使用 <code>JDBC</code> 访问关系型数据的时候, 其实也是使用 <code>DataFrameReader</code>, 对 <code>DataFrameReader</code> 提供一些配置, 就可以使用 <code>Spark</code> 访问 <code>JDBC</code>, 有如下几个配置可用</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">属性</th>
<th class="tableblock halign-left valign-top">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>url</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">要连接的 <code>JDBC URL</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>dbtable</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">要访问的表, 可以使用任何 <code>SQL</code> 语句中 <code>from</code> 子句支持的语法</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>fetchsize</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">数据抓取的大小(单位行), 适用于读的情况</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>batchsize</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">数据传输的大小(单位行), 适用于写的情况</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>isolationLevel</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">事务隔离级别, 是一个枚举, 取值 <code>NONE</code>, <code>READ_COMMITTED</code>, <code>READ_UNCOMMITTED</code>, <code>REPEATABLE_READ</code>, <code>SERIALIZABLE</code>, 默认为 <code>READ_UNCOMMITTED</code></p></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>读取数据集, 处理过后存往 <code>MySQL</code> 中的代码如下</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val spark = SparkSession
  .builder()
  .appName("hive example")
  .master("local[6]")
  .getOrCreate()

val schema = StructType(
  List(
    StructField("name", StringType),
    StructField("age", IntegerType),
    StructField("gpa", FloatType)
  )
)

val studentDF = spark.read
  .option("delimiter", "\t")
  .schema(schema)
  .csv("dataset/studenttab10k")

studentDF.write.format("jdbc").mode(SaveMode.Overwrite)
  .option("url", "jdbc:mysql://node01:3306/spark_test")
  .option("dbtable", "student")
  .option("user", "spark")
  .option("password", "Spark123!")
  .save()</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">运行程序</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>如果是在本地运行, 需要导入 <code>Maven</code> 依赖</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-xml hljs" data-lang="xml">&lt;dependency&gt;
    &lt;groupId&gt;mysql&lt;/groupId&gt;
    &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
    &lt;version&gt;5.1.47&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<p>如果使用 <code>Spark submit</code> 或者 <code>Spark shell</code> 来运行任务, 需要通过 <code>--jars</code> 参数提交 <code>MySQL</code> 的 <code>Jar</code> 包, 或者指定 <code>--packages</code> 从 <code>Maven</code> 库中读取</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">bin/spark-shell --packages  mysql:mysql-connector-java:5.1.47 --repositories http://maven.aliyun.com/nexus/content/groups/public/</code></pre>
</div>
</div>
</div>
</div>
</dd>
<dt class="hdlist1">从 <code>MySQL</code> 中读取数据</dt>
<dd>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p>读取 <code>MySQL</code> 的方式也非常的简单, 只是使用 <code>SparkSQL</code> 的 <code>DataFrameReader</code> 加上参数配置即可访问</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">spark.read.format("jdbc")
  .option("url", "jdbc:mysql://node01:3306/spark_test")
  .option("dbtable", "student")
  .option("user", "spark")
  .option("password", "Spark123!")
  .load()
  .show()</code></pre>
</div>
</div>
<div class="paragraph">
<p>默认情况下读取 <code>MySQL</code> 表时, 从 <code>MySQL</code> 表中读取的数据放入了一个分区, 拉取后可以使用 <code>DataFrame</code> 重分区来保证并行计算和内存占用不会太高, 但是如果感觉 <code>MySQL</code> 中数据过多的时候, 读取时可能就会产生 <code>OOM</code>, 所以在数据量比较大的场景, 就需要在读取的时候就将其分发到不同的 <code>RDD</code> 分区</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">属性</th>
<th class="tableblock halign-left valign-top">含义</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>partitionColumn</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">指定按照哪一列进行分区, 只能设置类型为数字的列, 一般指定为 <code>ID</code></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>lowerBound</code>, <code>upperBound</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">确定步长的参数, <code>lowerBound - upperBound</code> 之间的数据均分给每一个分区, 小于 <code>lowerBound</code> 的数据分给第一个分区, 大于 <code>upperBound</code> 的数据分给最后一个分区</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>numPartitions</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">分区数量</p></td>
</tr>
</tbody>
</table>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">spark.read.format("jdbc")
  .option("url", "jdbc:mysql://node01:3306/spark_test")
  .option("dbtable", "student")
  .option("user", "spark")
  .option("password", "Spark123!")
  .option("partitionColumn", "age")
  .option("lowerBound", 1)
  .option("upperBound", 60)
  .option("numPartitions", 10)
  .load()
  .show()</code></pre>
</div>
</div>
<div class="paragraph">
<p>有时候可能要使用非数字列来作为分区依据, <code>Spark</code> 也提供了针对任意类型的列作为分区依据的方法</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">val predicates = Array(
  "age &lt; 20",
  "age &gt;= 20, age &lt; 30",
  "age &gt;= 30"
)

val connectionProperties = new Properties()
connectionProperties.setProperty("user", "spark")
connectionProperties.setProperty("password", "Spark123!")

spark.read
  .jdbc(
    url = "jdbc:mysql://node01:3306/spark_test",
    table = "student",
    predicates = predicates,
    connectionProperties = connectionProperties
  )
  .show()</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>SparkSQL</code> 中并没有直接提供按照 <code>SQL</code> 进行筛选读取数据的 <code>API</code> 和参数, 但是可以通过 <code>dbtable</code> 来曲线救国, <code>dbtable</code> 指定目标表的名称, 但是因为 <code>dbtable</code> 中可以编写 <code>SQL</code>, 所以使用子查询即可做到</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-scala hljs" data-lang="scala">spark.read.format("jdbc")
  .option("url", "jdbc:mysql://node01:3306/spark_test")
  .option("dbtable", "(select name, age from student where age &gt; 10 and age &lt; 20) as stu")
  .option("user", "spark")
  .option("password", "Spark123!")
  .option("partitionColumn", "age")
  .option("lowerBound", 1)
  .option("upperBound", 60)
  .option("numPartitions", 10)
  .load()
  .show()</code></pre>
</div>
</div>
</div>
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
        </div>
      </div>
    </body>
  </html>
